Feron's BLOG

Tips Of Golang coding

  • 如果你不想使用调试器,你可以按照下面的一些有用的方法来达到基本调试的目的:

      1. 在合适的位置使用打印语句输出相关变量的值(print/printlnfmt.Print/fmt.Println/fmt.Printf)。
      1. fmt.Printf 中使用下面的说明符来打印有关变量的相关信息:
        %+v 打印包括字段在内的实例的完整信息
        %#v 打印包括字段和限定类型名称在内的实例的完整信息
        %T 打印某个类型的完整说明
      1. 使用 panic 语句来获取栈跟踪信息(直到 panic 时所有被调用函数的列表)。
      1. 使用关键字 defer 来跟踪代码执行过程。
  • _ 本身就是一个特殊的标识符,被称为空白标识符。它可以像其他标识符那样用于变量的声明或赋值(任何类型都可以赋值给它),但任何赋给这个标识符的值都将被抛弃,因此这些值不能在后续的代码中使用,也不可以使用这个标识符作为变量对其它变量进行赋值或运算。
  • 你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package mainpackage main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。
  • 当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 private )。
    (大写字母可以使用任何 Unicode 编码的字符,比如希腊文,不仅仅是 ASCII 码中的大写字母)。
    因此,在导入一个外部包后,能够且只能够访问该包中导出的对象。
  • main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。如果你的 main 包的源代码没有包含 main 函数,则会引发构建错误 undefined: main.main。main 函数既没有参数,也没有返回类型(与 C 家族中的其它语言恰好相反)。如果你不小心为 main 函数添加了参数或者返回类型,将会引发构建错误:func main must have no arguments and no return values results.
  • Go 语言中不存在类型继承。
    函数也可以是一个确定的类型,就是以函数作为返回类型。这种类型的声明要写在函数名和可选的参数列表之后,例如:

    1
    func FunctionName (a typea, b typeb) typeFunc
  • 一个函数可以拥有多返回值,返回类型之间需要使用逗号分割,并使用小括号 () 将它们括起来,如:

    1
    func FunctionName (a typea, b typeb) (t1 type1, t2 type2)

多返回值一般用于判断某个函数是否执行成功(true/false)或与其它返回值一同返回错误消息(详见之后的并行赋值)。

  • Go 语言不存在隐式类型转换,因此所有的转换都必须显式说明,就像调用一个函数一样(类型在这里的作用可以看作是一种函数):
    1
    valueOfTypeB = typeB(valueOfTypeA)

类型 B 的值 = 类型 B(类型 A 的值)

示例:

1
2
a := 5.0
b := int(a)

  • 反斜杠 \ 可以在常量表达式中作为多行的连接符使用

    1
    2
    const Ln2= 0.693147180559945309417232121458\
    176568075500134360255254120680009
  • iota 可以被用作枚举值:

    1
    2
    3
    4
    5
    const (
    a = iota
    b = iota
    c = iota
    )

第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1;所以 a=0, b=1, c=2 可以简写为如下形式:

1
2
3
4
5
const (
a = iota
b
c
)

iota 也可以用在表达式中,如:iota + 50。在每遇到一个新的常量块或单个常量声明时, iota 都会重置为 0(简单地讲,每遇到一次 constiota 就重置为 0)。

  • 指针的一个高级应用是你可以传递一个变量的引用(如函数的参数),这样不会传递变量的拷贝。指针传递是很廉价的,只占用 4 个或 8 个字节。当程序在工作中需要占用大量的内存,或很多变量,或者两者都有,使用指针会减少内存占用和提高效率。被指向的变量也保存在内存中,直到没有任何指针指向它们,所以从它们被创建开始就具有相互独立的生命周期。有点类似于Linux中的软连接 ln -s
  • 函数也可以以申明的方式被使用,作为一个函数类型,就像:
    1
    type binOp func(int, int) int

函数是一等值( first-class value):它们可以赋值给变量,就像 add := binOp 一样。

这个变量知道自己指向的函数的签名,所以给它赋一个具有不同签名的函数值是不可能的。

函数值(functions value)之间可以相互比较:如果它们引用的是相同的函数或者都是 nil 的话,则认为它们是相同的函数。函数不能在其它函数里面声明(不能嵌套),不过我们可以通过使用匿名函数来破除这个限制。

  • 如果函数的最后一个参数是采用 ...type 的形式,那么这个函数就可以处理一个变长的参数,这个长度可以为 0,这样的函数称为变参函数。
    func myFunc(a, b, arg ...int) {}
    这个函数接受一个类似某个类型的 slice 的参数,该参数可以通过 for 循环结构迭代。
    示例函数和调用:

    1
    2
    func Greeting(prefix string, who ...string)
    Greeting("hello:", "Joe", "Anna", "Eileen")
  • 如果变长参数的类型并不是都相同的呢?使用 5 个参数来进行传递并不是很明智的选择,有 2 种方案可以解决这个问题.

      1. 定义一个结构类型,假设它叫 Options,用以存储所有可能的参数:
        1
        2
        3
        4
        5
        type Options struct {
        par1 type1,
        par2 type2,
        ...
        }

      函数 F1 可以使用正常的参数 a 和 b,以及一个没有任何初始化的 Options 结构: F1(a, b, Options {})。如果需要对选项进行初始化,则可以使用 F1(a, b, Options {par1:val1, par2:val2})

      1. 使用空接口:

      如果一个变长参数的类型没有被指定,则可以使用默认的空接口 interface{},这样就可以接受任何类型的参数(详见第 11.9 节)。该方案不仅可以用于长度未知的参数,还可以用于任何不确定类型的参数。一般而言我们会使用一个 for-range 循环以及 switch 结构对每个参数的类型进行判断:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      func typecheck(..,..,values … interface{}) {
      for _, value := range values {
      switch v := value.(type) {
      case int: …
      case float: …
      case string: …
      case bool: …
      default: …
      }
      }
      }
  • 关键字 defer 允许我们推迟到函数返回之前(或任意位置执行 return 语句之后,或者函数执行到最后)一刻才执行某个语句或函数(为什么要在返回之后才执行这些语句?因为 return 语句同样可以包含一些操作,而不是单纯地返回某个值)。
    当有多个 defer 行为被注册时,它们会以逆序执行(类似栈,即后进先出):

    1
    2
    3
    4
    5
    func f() {
    for i := 0; i < 5; i++ {
    defer fmt.Printf("%d ", i)
    }
    }

    上面的代码将会输出:4 3 2 1 0。

  • 一个字符串本质上是一个字节数组
    从字符串生成字节切片,可以直接通过 c := []byte(s) 来获取一个字节的切片
    通过代码 len([]int32(s)) 来获得字符串中字符的数量,但使用 utf8.RuneCountInString(s) 效率会更高一点

未完待续…